For class variables, lookup type in base classes (#1338, #2022, #2211)#2380
Conversation
|
I am having a hard time figuring out how TypeVars could be resolved. Any pointers (given the rest of the patch is a good idea) would be very welcome. For now I simply disabled inheriting TypeVars from the base class, and possibly it is even something for another PR. |
This avoids looking into the base class of a base class while the type has been changed
|
I'm excited that you're working on this! I'm not sure I can answer your questions (maybe @JukkaL can help). Personally I'm not sure whether type variables declared in a class should ever be overridden. However I don't see any tests for generic classes -- surely there are all sorts of interesting cases when the base class is generic over T and the class variable's type involves T, and then the subclass either substitutes a specific type for T, or a different type variable, etc... regarding |
|
Found a way to lookup TypeVars. Added some test cases for it, let me know if you can think of more juicy ones.
|
python/mypy#2380 showed a discrepancy between object and FunctionType in stdlib2. The first defined __doc__ to be str, the second Optional[str]. As FunctionType depends on object, this is no longer valid. As suggested by @gvanrossum in python/mypy#2380, all __doc__ should be considered Optional. (Final verdict was just to remove most __doc__ attributes since it's inherited from object.)
|
You can probably use Example where this would be useful: If that doesn't work for some reason, it's also okay to reject assignments to class variables with type variables in them and file an issue about supporting them. |
|
I'm not sure it's a good idea to make those errors. I expect those to be
common, as stand-in for instance variables. (Also encouraged by PEP 526.)
…--Guido (mobile)
|
|
I indeed found out that This should implement all cases I could find (including TypeVars). Let me know if you can think up any more test cases to include! |
gvanrossum
left a comment
There was a problem hiding this comment.
This is a pretty neat small change! I have a few questions.
| class A: | ||
| a = 1 # type: int | ||
| class B(A): | ||
| a = None |
There was a problem hiding this comment.
Hm, this would become invalid with strict none checking. Maybe just use another int value?
| a = "a" | ||
| [out] | ||
| main: note: In class "B": | ||
| main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") |
There was a problem hiding this comment.
In a large class hierarchy this error could be pretty mysterious. Can you figure out a way to at least link to the superclass where the variable is declared first? (Even better the exact line, but that may be tricky.)
There was a problem hiding this comment.
I wonder if this is an issue for only class variables; in general it would be nice if we can tell where the original declaration came from. As the same issue is already true for when using "self.a = 1" for example. I will fiddle with this a bit, see what I can come up with.
| class A: | ||
| a = None # type: int | ||
| class B(A): | ||
| a = None # type: str |
There was a problem hiding this comment.
Hm, are you sure that this (without caring about C even) should be allowed? I think it's just as much an error as when no type is given. (However, the declared type here should be allowed to be a subclass of the type declared earlier.)
There was a problem hiding this comment.
Class variables are invariant, so the type should be equivalent (is_equivalent) to the base class type. Example:
class A:
a = None # type: object
def f(self) -> None:
self.a = 'x'
class B(A):
a = None # type: int
def g(self) -> None:
print(self.a + 1) # ouch, could be str
|
Another idea for more tests: the superclass could have a method; a subclass should not be able to override this with a variable. And vice versa. |
…pe of a base class
|
I always love how adding a test case can show you other issues. Fixed all issues I am aware off. I also added that if a superclass defines a |
|
Splitted off the "find where a type was first declared" into a bug ticket and a PR in my fork. Hope you don't mind, but I would like to resolve it a bit more generic :) |
|
Found one additional issue. Line 35 defines I have no idea how to resolve this cleanly, as I cannot figure out what 'args' refers to. Mainly because the Python source does not reference it. Any suggestions would be appreciated. |
|
Ah, I see what this is. I don't know how this ended up in the stubs but the solution is to just remove these from the ConfigParser stub. (For bonus points, change it to |
|
Um, you synced typeshed one commit before the fix you applied. You should probably do something like this: |
|
Oops, indeed, I forgot to rebase my master branch against upstream. Silly mistake :D Tnx! |
|
@TrueBrain This looks good to me. If you fix the merge conflict (should be easy) this can be merged. Optionally, implement/test redefining an attribute with an |
|
Added #2500 as a potential follow-up task based on above discussion about showing the class that originally defined the attribute. |
python/mypy#2380 showed a discrepancy between object and FunctionType in stdlib2. The first defined __doc__ to be str, the second Optional[str]. As FunctionType depends on object, this is no longer valid. As suggested by @gvanrossum in python/mypy#2380, all __doc__ should be considered Optional. (Final verdict was just to remove most __doc__ attributes since it's inherited from object.)
With this patch, class variables are validated for their type if only the base class defines the type.
Typeshed shows 1 issue in the pyi files. doc is set by MyPy to be 'str'. In stdlib/2/types.pyi, in FunctionType, it is set to func_doc, which is 'Optional[str]'. Because with this patch assignments are checked against their definition, assigning 'Optional[str]' to 'str' is incompatible.
The solution appears simple, by doing types.pyi similar as stdlib/3: not using a level of indirection when assigning these types.
So instead of:
Doing:
If you like, I can make a PR for that change too; but I was first more curious if this PR is correct.